/*                          UX-Framework                               
 *                                                                 
 *  iZE Co.,Ltd. Copyright 2009 All rights reserved.          
 *  This file is distributed under the terms of the GNU Lesser 
 *  General Public License, described in file LICENSE.                                     
 */

package jp.ize.ux.acts;

// {{{ imports
import jp.ize.ux.UXObject;
import jp.ize.ux.geom.UXPoint;
import jp.ize.ux.acts.Act;
import jp.ize.ux.utils.Util;
import jp.ize.ux.tween.UXTweenManager;
import jp.ize.ux.tween.UXTween;
// }}}
// {{{ typedef ArcStateType
/**
 * ArcActが必要な関数、プロパティの定義です。
 */
typedef ArcStateType = {
	> ListStateType,
	/** 中心座標 */
	var center(default, default) : UXPoint;
	/** 半径 */
	var radius(default, default) : Float;
	/** 回転具合 */
	var theta(default, default) : Float;
	/** 開始角 */
	var starta(default, default) : Float;
	/** 終了角 */
	var enda(default, default) : Float;
	/** 子要素ごとの回転具合 */
	var thetas(default, default) : Array<Float>;
}
// }}}
// {{{ typedef ArcRotateParameter > ActParameter
/**
 * 回転パラメータを表現するクラス。
 */
typedef ArcRotateParameter =
{
	> ActParameter,
	// {{{ var angle : Null<Int>;
	/**
	 * 回転量を角度で表現した数値です。
	 * 正の値は時計回り、負の値は反時計回りを意味します。
	 */
	var angle : Null<Int>;
	// }}}
	// {{{ var omega : Null<Float>;
	/**
	 * 回転量を示す数値です。
	 */
	var omega : Null<Float>;
	// }}}
}
// }}}
// {{{ class ArcAct extends Act
/**
 * ArcAct クラス<br>
 * 円上に配置された動きを表現するクラス。中心座標を持つ状態を仮定します。<br>
 *
 * @author		iZE Co., Ltd.<br>
 * @version		$Id$<br>
 */
class ArcAct extends Act
{
	// {{{ public function new()
	/**
	 * コンストラクタ。
	 */
	public function new()
	{
		super();
	}
	// }}}
	// {{{ public function layout( ap:ActParameter, ?interp:ArcStateType->ActParameter ) : ArcStateType -> Void
	/**
	 * 子コンポーネントをリング状に配置するアクションです。
	 */
	public function layout( ap:ActParameter, ?interp:ArcStateType->ActParameter ) : ArcStateType -> Void
	{
		var me = this;
		return function( state : ArcStateType ) {
			var param = ap;
			if ( interp!=null ) param = interp(state);
			var delta = (state.enda - state.starta)*Math.PI/180;
			var theta_s = state.theta + state.starta*Math.PI/180;
			var omegas:Array<Float> = new Array();
			me.retouchZ(state,state.children.length-1);
			for (i in 0...state.children.length) {
				omegas[i] = theta_s + i*delta/(state.children.length-1);
			}
			me.doLayout(state,param,omegas);
		};
	}
	// }}}
	// {{{ public function move( ?mp:MoveParameter, ?interp:ArcStateType->MoveParameter ) : ArcStateType -> Void
	/**
	 * 子コンポーネントを移動量分だけ移動させるアクションです。
	 * stateのcyclicがtrueの場合でないと動作しません。
	 */
	public function move( ?mp:MoveParameter, ?interp:ArcStateType->MoveParameter ) : ArcStateType -> Void
	{
		var me = this;
		return function( state : ArcStateType ) : Void {
			if ( state.cyclic ) {
				var param = mp;
				if ( interp!=null ) param = interp(state);

				var omegas:Array<Float> = new Array();
				var delta = (state.enda - state.starta)*Math.PI/180;
				var so:Float = state.starta*Math.PI/180;
				var eo:Float = state.enda*Math.PI/180;
				var delta_o:Float = 30*Math.PI/180;
				var start_x:Float = state.center.x + state.radius * Math.cos(so-delta_o);
				var start_y:Float = state.center.y - state.radius * Math.sin(so-delta_o);
				var end_x:Float   = state.center.x + state.radius * Math.cos(eo+delta_o);
				var end_y:Float   = state.center.y - state.radius * Math.sin(eo+delta_o);
				var cs:Array<UXObject> = state.children.copy();
				for ( i in 0...state.children.length ) {
					var dir:Int = 0;
					var idx:Int = 0;
					if ( i+param.delta<0 ) {
						idx = i+param.delta+state.children.length;
						dir = -1 * Std.int(Math.abs(param.delta));
					} else if ( i+param.delta>state.children.length-1 ) {
						idx = (i+param.delta)-state.children.length;
						dir = Std.int(Math.abs(param.delta));
					} else {
						idx = i+param.delta;
					}
					state.children[i] = cs[idx];
					if ( dir>0 ) {
						state.children[i].x = end_x;
						state.children[i].y = end_y;
					} else if ( dir<0 ) {
						state.children[i].x = start_x;
						state.children[i].y = start_y;
					}
					omegas.push( so+idx*delta/(state.children.length-1) );
				}
				me.doLayout(state,param,omegas);
				me.retouchZ(state,state.children.length-1);
			}
		};
	}
	// }}}
	// {{{ public function wipe( wp:WipeParameter, ?interp:ArcStateType->WipeParameter ) : ArcStateType -> Void
	/**
	 * 直線上を子コンポーネントが重なるように移動するアクションです。
	 */
	public function wipe( wp:WipeParameter, ?interp:ArcStateType->WipeParameter ) : ArcStateType -> Void
	{
		var me = this;
		return function( state : ArcStateType ) : Void {
			var param = wp;
			if ( interp!=null ) param = interp(state);
			var delta = (state.enda - state.starta)*Math.PI/180;
			var theta_s = state.theta + state.starta*Math.PI/180;
			var omegas:Array<Float> = new Array();
			me.retouchZ(state,param.center);
			for (i in 0...state.children.length) {
				omegas[i] = theta_s + param.center*delta/(state.children.length-1);
			}
			me.doLayout(state,param,omegas);
		};
	}
	// }}}
	// {{{ public function fishEye( fp:FishEyeParameter, ?interp:ArcStateType->FishEyeParameter ) : ArcStateType -> Void
	/**
	 * マウスの動きに合わせて魚眼ズームするアクションです。周りの子コンポーネントはつられて押し出されます。
	 */
	public function fishEye( fp:FishEyeParameter, ?interp:ArcStateType->FishEyeParameter ) : ArcStateType -> Void
	{
		var me = this;
		return function( state : ArcStateType ) : Void {
			var param = fp;
			if ( interp!=null ) param = interp(state);
			var centerScale:Float = param.centerScale;
			var effectRange:Int   = param.effectRange;
			var theta_s = state.theta + state.starta*Math.PI/180;
			var delta = (state.enda - state.starta)*Math.PI/180;
			var scaleCenterIdx:Int = -1;
			var scales:Array<Float> = new Array();
			var dists:Array<Float> = new Array();
			for (i in 0...state.children.length) {
				if ( state.children[i]==state.eventTarget ) {
					scaleCenterIdx = i;
				}
				scales[i] = 1.0;
				dists[i] = i;
			}
			var s_range:Int = scaleCenterIdx - effectRange;
			var e_range:Int = scaleCenterIdx + effectRange;
			var d_scale:Float = (centerScale-1.0)/2/effectRange;
			for ( i in -effectRange...effectRange+1 ) {
				var idx:Int = scaleCenterIdx+i;
				if ( idx>=0 && idx<state.children.length ) {
					if ( i<0 ) {
						scales[idx] = 1.0 + (i+effectRange+1)*d_scale;
						dists[idx]  += (scales[idx]-1.0)/(effectRange*1.5);
					} else if ( i==0 ) {
						scales[idx] = centerScale;
					} else if ( i>0 ) {
						scales[idx] = 1.0 + (effectRange-i+1)*d_scale;
						dists[idx]  += (scales[idx]-1.0)/(effectRange*1.5);
					}
				}
			}
			for (i in 0...state.children.length) {
				var c = state.children[i];
				c.scaleX = scales[i];
				c.scaleY = scales[i];
				var goal_x:Float = state.center.x
						+ state.radius * Math.cos(theta_s + dists[i]*delta/(state.children.length-1));
				var goal_y:Float = state.center.y
						- state.radius * Math.sin(theta_s + dists[i]*delta/(state.children.length-1));
				c.x = goal_x;
				c.y = goal_y;
			}
			me.retouchZ(state,scaleCenterIdx);
			if ( state.subject!=null ) {
				state.subject.notify(Act.STOP);
			}
		};
	}
	// }}}
	// {{{ public function doLayout( state : ArcStateType, param:Dynamic, omegas:Array<Float> ) : Void
	/**
	 * 子コンポーネントを子コンポーネントのリサイズポリシーに従って円上に配置します。
	 */
	public function doLayout( state : ArcStateType, param:Dynamic, omegas:Array<Float> ) : Void
	{
		var me = this;
		initSync(state.children.length);
		setSyncDoneHandler( function(){
			if ( state.subject!=null ) {
				state.subject.notify(Act.STOP);
			}
		});
		for (i in 0...state.children.length) {
			var c = state.children[i];
			var getter = function(obj:ArcStateType): {v:Float} {
				return {v:obj.thetas[i]};
			};
			var setter = function(obj:ArcStateType, init:{v:Float}, end:{v:Float}, v:Float) {
				obj.thetas[i] = init.v+(end.v-init.v)*v;
				c.x = obj.center.x + obj.radius * Math.cos(obj.thetas[i]);
				c.y = obj.center.y - obj.radius * Math.sin(obj.thetas[i]);
			};
			var end = {v:omegas[i]};
			var tw = new UXTween<ArcStateType,{v:Float}>(state, end, getter, setter, param.time,
						param.transition);
			tw.setEndTweenHandler( function(e:Dynamic){
				me.doneSync(i);
			});
			var id = "arclayout" + Std.string(i);
			if ( !me.tm.objects.exists(id) ) {
				me.tm.registObject(id,state);
			}
			me.tm.addTweenChain(id,tw); 
			me.tm.start(id,false,state.target);
		}
	}
	// }}}
	// {{{ public function retouchZ( state:ArcStateType, center:Int ) : Void
	/**
	 * 子要素の前後関係を調整します
	 */
	public function retouchZ( state:ArcStateType, center:Int ) : Void
	{
		var ary:Array<Int> = new Array();
		var cnt:Int = 1;
		ary.push(center);
		while ( cnt<state.children.length ) {
			if ( center-cnt>=0 ) {
				ary.push(center-cnt);
			}
			if ( center+cnt<state.children.length ) {
				ary.push(center+cnt);
			}
			cnt++;
		}
		ary.reverse();
		for ( i in 0...state.children.length ) {
			var c = state.children[ary[i]];
			state.children[0].parent.setChildIndex( c, i );
		}
	}

	// }}}
}
// }}}
